/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/serializer/JavaBeanSerializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: JavaBeanSerializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json.serializer;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.elancom.framework.commons.json.JSONException;
import com.elancom.framework.commons.json.util.FieldInfo;
import com.elancom.framework.commons.json.util.TypeUtils;

/**
 * @author elancom
 */
public class JavaBeanSerializer implements ObjectSerializer {

	// serializers
	private final FieldSerializer[] getters;

	public FieldSerializer[] getGetters() {
		return getters;
	}

	public JavaBeanSerializer(Class<?> clazz) {
		this(clazz, (Map<String, String>) null);
	}

	public JavaBeanSerializer(Class<?> clazz, String... aliasList) {
		this(clazz, createAliasMap(aliasList));
	}

	static Map<String, String> createAliasMap(String... aliasList) {
		final Map<String, String> aliasMap = new HashMap<String, String>();
		for (final String alias : aliasList) {
			aliasMap.put(alias, alias);
		}

		return aliasMap;
	}

	public JavaBeanSerializer(Class<?> clazz, Map<String, String> aliasMap) {
		final List<FieldSerializer> getterList = new ArrayList<FieldSerializer>();

		final List<FieldInfo> fieldInfoList = TypeUtils.computeGetters(clazz, aliasMap);

		for (final FieldInfo fieldInfo : fieldInfoList) {
			getterList.add(createFieldSerializer(fieldInfo));
		}

		//
		getters = getterList.toArray(new FieldSerializer[getterList.size()]);
	}

	protected boolean isWriteClassName(JSONSerializer serializer, Object obj, Type fieldType, Object fieldName) {
		return serializer.isWriteClassName(fieldType, obj);
	}

	@Override
	public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {
		final SerializeWriter out = serializer.getWriter();

		if (object == null) {
			out.writeNull();
			return;
		}

		if (serializer.containsReference(object)) {
			writeReference(serializer, object);
			return;
		}

		final FieldSerializer[] getters = this.getters;

		if (out.isEnabled(SerializerFeature.SortField)) {
			Arrays.sort(getters);
		}

		final SerialContext parent = serializer.getContext();
		serializer.setContext(parent, object, fieldName);

		try {
			out.append('{');

			if (getters.length > 0 && out.isEnabled(SerializerFeature.PrettyFormat)) {
				serializer.incrementIndent();
				serializer.println();
			}

			boolean commaFlag = false;

			if (isWriteClassName(serializer, object, fieldType, fieldName)) {
				final Class<?> objClass = object.getClass();
				if (objClass != fieldType) {
					out.writeFieldName("@type");
					serializer.write(object.getClass());
					commaFlag = true;
				}
			}

			for (int i = 0; i < getters.length; ++i) {
				final FieldSerializer fieldSerializer = getters[i];

				if (serializer.isEnabled(SerializerFeature.SkipTransientField)) {
					final Field field = fieldSerializer.getField();
					if (field != null) {
						if (Modifier.isTransient(field.getModifiers())) {
							continue;
						}
					}
				}

				Object propertyValue = fieldSerializer.getPropertyValue(object);

				if (!FilterUtils.apply(serializer, object, fieldSerializer.getName(), propertyValue)) {
					continue;
				}

				final String key = FilterUtils.processKey(serializer, object, fieldSerializer.getName(), propertyValue);

				final Object originalValue = propertyValue;
				propertyValue = FilterUtils.processValue(serializer, object, fieldSerializer.getName(), propertyValue);

				if (propertyValue == null) {
					if ((!fieldSerializer.isWriteNull()) && (!serializer.isEnabled(SerializerFeature.WriteMapNullValue))) {
						continue;
					}
				}

				if (commaFlag) {
					out.append(',');
					if (out.isEnabled(SerializerFeature.PrettyFormat)) {
						serializer.println();
					}
				}

				if (key != fieldSerializer.getName()) {
					out.writeFieldName(key);
					serializer.write(propertyValue);
				} else if (originalValue != propertyValue) {
					fieldSerializer.writePrefix(serializer);
					serializer.write(propertyValue);
				} else {
					fieldSerializer.writeProperty(serializer, propertyValue);
				}

				commaFlag = true;
			}

			if (getters.length > 0 && out.isEnabled(SerializerFeature.PrettyFormat)) {
				serializer.decrementIdent();
				serializer.println();
			}

			out.append('}');
		} catch (final Exception e) {
			throw new JSONException("write javaBean error", e);
		} finally {
			serializer.setContext(parent);
		}
	}

	public void writeReference(JSONSerializer serializer, Object object) {
		serializer.writeReference(object);
	}

	public FieldSerializer createFieldSerializer(FieldInfo fieldInfo) {
		final Class<?> clazz = fieldInfo.getFieldClass();

		if (clazz == Number.class) {
			return new NumberFieldSerializer(fieldInfo);
		}

		return new ObjectFieldSerializer(fieldInfo);
	}
}
